package com.tqyan.service.transfer;

import com.alibaba.fastjson.JSONObject;
import com.tqyan.model.HttpClientResult;
import com.tqyan.model.exp.DefaultException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.*;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.stereotype.Component;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.Map.Entry;

/**
 * @description HttpClient服务
 * <p> <功能详细描述> </p>
 *
 * @author 陈晨
 * @date 2023/4/12 16:11
 */
@Configuration
@Component
@ConditionalOnClass(value = {RestTemplate.class, CloseableHttpClient.class})
@Slf4j
public class HttpClientService {

    private static final int DEFAULT_POOL_MAX_TOTAL = 1000;
    private static final int DEFAULT_POOL_MAX_PER_ROUTE = 1000;

    private static final int DEFAULT_CONNECT_TIMEOUT = 5000;
    private static final int DEFAULT_CONNECT_REQUEST_TIMEOUT = 500;
    private static final int DEFAULT_SOCKET_TIMEOUT = 5000;

    private static final int RETRY_TIMES = 2;

    /** 针对不同的地址,特别设置不同的长连接保持时间 */
    private Map<String,Integer> keepAliveTargetHost;
    /** 针对不同的地址,特别设置不同的长连接保持时间,单位 s */
    private int keepAliveTime = 60;
    @Autowired
    private CloseableHttpClient httpClient;

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    /**
     * @description 创建HTTP客户端工厂
     * <p> <功能详细描述> </p>
     *
     * @author 陈晨
     * @date 2023/4/12 16:12
     */
    @Bean(name = "clientHttpRequestFactory")
    public ClientHttpRequestFactory clientHttpRequestFactory() {
        /** maxTotalConnection 和 maxConnectionPerRoute 必须要配 */
        HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
        // 连接超时
        clientHttpRequestFactory.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT);
        // 数据读取超时时间，即SocketTimeout
        clientHttpRequestFactory.setReadTimeout(DEFAULT_SOCKET_TIMEOUT);
        // 从连接池获取请求连接的超时时间，不宜过长，必须设置，比如连接不够用时，时间过长将是灾难性的
        clientHttpRequestFactory.setConnectionRequestTimeout(DEFAULT_CONNECT_REQUEST_TIMEOUT);
        return clientHttpRequestFactory;
    }

    /**
     * @description 初始化RestTemplate,并加入spring的Bean工厂，由spring统一管理
     * <p> <功能详细描述> </p>
     *
     * @author 陈晨
     * @date 2023/4/12 16:12
     */
    @Bean(name = "httpClientTemplate")
    public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
        RestTemplate restTemplate = new RestTemplate(factory);
        // 采用RestTemplate内部的MessageConverter
        // 重新设置StringHttpMessageConverter字符集，解决中文乱码问题
        this.modifyDefaultCharset(restTemplate);
        //设置错误处理器
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler());
        return restTemplate;
    }

    /**
     * @description 设置默认的字符集类型为UTF-8
     * <p> <功能详细描述> </p>
     *
     * @author 陈晨
     * @date 2023/4/12 16:51
     */
    private void modifyDefaultCharset(RestTemplate restTemplate) {
        List<HttpMessageConverter<?>> converterList = restTemplate.getMessageConverters();
        HttpMessageConverter<?> converterTarget = null;
        for (HttpMessageConverter<?> item : converterList) {
            if (StringHttpMessageConverter.class == item.getClass()) {
                converterTarget = item;
                break;
            }
        }
        if (null != converterTarget) {
            converterList.remove(converterTarget);
        }
        Charset defaultCharset = StandardCharsets.UTF_8;
        converterList.add(1, new StringHttpMessageConverter(defaultCharset));
    }

    /**
     * @description 配置httpClient
     * <p> <功能详细描述> </p>
     *
     * @author 陈晨
     * @date 2023/4/12 16:13
     */
    @Bean
    public CloseableHttpClient httpClient() {
        try {
            HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
            //设置信任ssl访问
            SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, (arg0, arg1) -> true).build();
            httpClientBuilder.setSSLContext(sslContext);
            HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
            SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
            Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                    // 注册http和https请求
                    .register("http", PlainConnectionSocketFactory.getSocketFactory())
                    .register("https", sslConnectionSocketFactory).build();

            //使用Httpclient连接池的方式配置(推荐)，同时支持netty，okHttp以及其他http框架
            PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
            // 最大连接数
            poolingHttpClientConnectionManager.setMaxTotal(DEFAULT_POOL_MAX_TOTAL);
            // 同路由并发数
            poolingHttpClientConnectionManager.setDefaultMaxPerRoute(DEFAULT_POOL_MAX_PER_ROUTE);
            //配置连接池
            httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager);
            // 重试次数
            httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(RETRY_TIMES, false));
            //设置默认请求头
            List<Header> headers = this.getDefaultHeaders();
            httpClientBuilder.setDefaultHeaders(headers);
            return httpClientBuilder.build();
        } catch (Exception e) {
            log.error("[HTTP] 初始化连接池异常: {}", e.getMessage(), e);
            throw new DefaultException(e.getMessage());
        }
    }

    /**
     * @description 配置长连接保持策略
     * <p> <功能详细描述> </p>
     *
     * @author 陈晨
     * @date 2023/4/12 16:14
     */
//    public ConnectionKeepAliveStrategy connectionKeepAliveStrategy() {
//        return (response, context) -> {
//            // Honor 'keep-alive' header
//            HeaderElementIterator it = new BasicHeaderElementIterator(
//                    response.headerIterator(HTTP.CONN_KEEP_ALIVE));
//            while (it.hasNext()) {
//                HeaderElement he = it.nextElement();
//                log.info("HeaderElement:{}", JSON.toJSONString(he));
//                String param = he.getName();
//                String value = he.getValue();
//                if (value != null && "timeout".equalsIgnoreCase(param)) {
//                    try {
//                        return Long.parseLong(value) * 1000;
//                    } catch (NumberFormatException ignore) {
//                        log.error("解析长连接过期时间异常", ignore);
//                    }
//                }
//            }
//            HttpHost target = (HttpHost) context.getAttribute(
//                    HttpClientContext.HTTP_TARGET_HOST);
//            //如果请求目标地址,单独配置了长连接保持时间,使用该配置
//            Optional<Entry<String, Integer>> any = httpClientPoolConfig.getKeepAliveTargetHost().entrySet().stream().filter(
//                    e -> e.getKey().equalsIgnoreCase(target.getHostName())).findAny();
//            //否则使用默认长连接保持时间
//            return any.map(en -> en.getValue() * 1000L).orElse(httpClientPoolConfig.getKeepAliveTime() * 1000L);
//        };
//    }

    /**
     * @description 发送get请求
     * <p> <功能详细描述> </p>
     *
     * @author 陈晨
     * @date 2023/4/12 16:14
     */
    public HttpClientResult doGet(String url) throws Exception {
        return this.doGet(url, null, null);
    }

    /**
     * @description 发送get请求
     * <p> <功能详细描述> </p>
     *
     * @author 陈晨
     * @date 2023/4/12 16:14
     */
    public HttpClientResult doGet(String url, Map<String, String> params) throws Exception {
        return this.doGet(url, null, params);
    }

    /**
     * @description 发送get请求
     * <p> <功能详细描述> </p>
     *
     * @author 陈晨
     * @date 2023/4/12 16:14
     */
    public HttpClientResult doGet(String url, Map<String, String> headers, Map<String, String> params) throws Exception {
        // 设置请求参数
        URIBuilder uriBuilder = new URIBuilder(url);
        if (params != null) {
            Set<Entry<String, String>> entrySet = params.entrySet();
            for (Entry<String, String> entry : entrySet) {
                uriBuilder.setParameter(entry.getKey(), entry.getValue());
            }
        }
        // 创建http对象
        HttpGet httpGet = new HttpGet(uriBuilder.build());
        /**
         * setConnectTimeout：设置连接超时时间，单位毫秒。
         * setConnectionRequestTimeout：设置从connect Manager(连接池)获取Connection
         * 超时时间，单位毫秒。这个属性是新加的属性，因为目前版本是可以共享连接池的。
         * setSocketTimeout：请求获取数据的超时时间(即响应时间)，单位毫秒。 如果访问一个接口，多少时间内无法返回数据，就直接放弃此次调用。
         */
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(DEFAULT_CONNECT_TIMEOUT)
                .setConnectionRequestTimeout(DEFAULT_CONNECT_REQUEST_TIMEOUT)
                .setSocketTimeout(DEFAULT_SOCKET_TIMEOUT)
                .build();
        httpGet.setConfig(requestConfig);
        // 设置请求头
        this.packageHeader(headers, httpGet);
        // 执行请求并获得响应结果
        return this.getHttpClientResult(httpGet, StandardCharsets.UTF_8);
    }

    /**
     * @description 发送post请求
     * <p> <功能详细描述> </p>
     *
     * @author 陈晨
     * @date 2023/4/12 16:23
     */
    public HttpClientResult doPost(String url) throws Exception {
        return this.doPost(url, null, null, null, StandardCharsets.UTF_8);
    }

    /**
     * @description 发送post请求
     * <p> <功能详细描述> </p>
     *
     * @author 陈晨
     * @date 2023/4/12 16:23
     */
    public HttpClientResult doPost(String url, Map<String, String> params, String requestBody) throws Exception {
        return this.doPost(url, null, params, requestBody, StandardCharsets.UTF_8);
    }

    /**
     * @description 发送post请求
     * <p> <功能详细描述> </p>
     *
     * @author 陈晨
     * @date 2023/4/12 16:23
     */
    public HttpClientResult doPost(String url, Map<String, String> headers
            , Map<String, String> params, String requestBody, Charset charset)
            throws Exception {
        // 创建http对象
        HttpPost httpPost = new HttpPost(url);
        /**
         * setConnectTimeout：设置连接超时时间，单位毫秒。
         * setConnectionRequestTimeout：设置从connect Manager(连接池)获取Connection
         * 超时时间，单位毫秒。这个属性是新加的属性，因为目前版本是可以共享连接池的。
         * setSocketTimeout：请求获取数据的超时时间(即响应时间)，单位毫秒。 如果访问一个接口，多少时间内无法返回数据，就直接放弃此次调用。
         */
//        RequestConfig requestConfig = RequestConfig.custom()
//                .setConnectTimeout(DEFAULT_CONNECT_TIMEOUT)
////                .setConnectionRequestTimeout(DEFAULT_CONNECT_REQUEST_TIMEOUT)
//                .setSocketTimeout(DEFAULT_SOCKET_TIMEOUT * 10)
//                .build();
//        httpPost.setConfig(requestConfig);
        // 设置请求头
        this.packageHeader(headers, httpPost);
        // 设置请求参数
        this.packageParam(params, httpPost);
        if (StringUtils.isNotBlank(requestBody)) {
            httpPost.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
        }
        // 执行请求并获得响应结果
        return this.getHttpClientResult(httpPost, charset);
    }

    /**
     * @description 获取响应流
     * <p> <功能详细描述> </p>
     *
     * @author 陈晨
     * @date 2023/4/12 16:23
     */
    public InputStream getResponseStreamByPost(String url, Map<String, String> headers, Map<String, String> params, String requestBody) throws Exception {
        // 创建http对象
        HttpPost httpPost = new HttpPost(url);
        /**
         * setConnectTimeout：设置连接超时时间，单位毫秒。
         * setConnectionRequestTimeout：设置从connect Manager(连接池)获取Connection
         * 超时时间，单位毫秒。这个属性是新加的属性，因为目前版本是可以共享连接池的。
         * setSocketTimeout：请求获取数据的超时时间(即响应时间)，单位毫秒。 如果访问一个接口，多少时间内无法返回数据，就直接放弃此次调用。
         */
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(DEFAULT_CONNECT_TIMEOUT)
//                .setConnectionRequestTimeout(DEFAULT_CONNECT_REQUEST_TIMEOUT)
                .setSocketTimeout(DEFAULT_SOCKET_TIMEOUT)
                .build();
        httpPost.setConfig(requestConfig);
        // 设置请求头
        this.packageHeader(headers, httpPost);
        // 设置请求参数
        this.packageParam(params, httpPost);
        if (StringUtils.isNotBlank(requestBody)) {
            httpPost.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON));
        }
        // 执行请求并获得响应结果
        HttpResponse httpResponse = httpClient.execute(httpPost);
        return httpResponse.getEntity().getContent();
    }

    /**
     * @description 发送put请求
     * <p> <功能详细描述> </p>
     *
     * @author 陈晨
     * @date 2023/4/12 16:26
     */
    public HttpClientResult doPut(String url) throws Exception {
        return this.doPut(url, null);
    }

    /**
     * @description 发送put请求
     * <p> <功能详细描述> </p>
     *
     * @author 陈晨
     * @date 2023/4/12 16:26
     */
    public HttpClientResult doPut(String url, Map<String, String> params) throws Exception {
        HttpPut httpPut = new HttpPut(url);
        this.packageParam(params, httpPut);
        return this.getHttpClientResult(httpPut, StandardCharsets.UTF_8);
    }

    /**
     * @description 发送delete请求
     * <p> <功能详细描述> </p>
     *
     * @author 陈晨
     * @date 2023/4/12 16:31
     */
    public HttpClientResult doDelete(String url) throws Exception {
        HttpDelete httpDelete = new HttpDelete(url);
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(DEFAULT_CONNECT_TIMEOUT)
                .setConnectionRequestTimeout(DEFAULT_CONNECT_REQUEST_TIMEOUT)
                .setSocketTimeout(DEFAULT_SOCKET_TIMEOUT)
                .build();
        httpDelete.setConfig(requestConfig);
        return this.getHttpClientResult(httpDelete, StandardCharsets.UTF_8);
    }

    /**
     * @description 发送delete请求
     * <p> <功能详细描述> </p>
     *
     * @author 陈晨
     * @date 2023/4/12 16:31
     */
    public HttpClientResult doDelete(String url, Map<String, String> params) throws Exception {
        if (params == null) {
            params = new HashMap<>();
        }
        params.put("_method", "delete");
        return this.doPost(url, params, null);
    }

    /**
     * @description 封装请求头
     * <p> <功能详细描述> </p>
     *
     * @author 陈晨
     * @date 2023/4/12 16:46
     */
    public void packageHeader(Map<String, String> params, HttpRequestBase httpMethod) {
        if (null == params) {
            return;
        }
        for (Entry<String, String> entry : params.entrySet()) {
            httpMethod.setHeader(entry.getKey(), entry.getValue());
        }
    }

    /**
     * @description 封装请求参数
     * <p> <功能详细描述> </p>
     *
     * @author 陈晨
     * @date 2023/4/12 16:46
     */
    public void packageParam(Map<String, String> params, HttpEntityEnclosingRequestBase httpMethod) {
        if (null == params) {
            return;
        }
        List<NameValuePair> nvps = new ArrayList<>();
        Set<Entry<String, String>> entrySet = params.entrySet();
        for (Entry<String, String> entry : entrySet) {
            nvps.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
        }
        httpMethod.setEntity(new UrlEncodedFormEntity(nvps, StandardCharsets.UTF_8));
    }

    /**
     * @description 获取响应结果
     * <p> <功能详细描述> </p>
     *
     * @author 陈晨
     * @date 2023/4/12 16:47
     */
    public HttpClientResult getHttpClientResult(HttpRequestBase httpMethod, Charset charset) throws Exception {
        // 执行请求, 调用处释放
        CloseableHttpResponse httpResponse = null;
        try {
            httpResponse = httpClient.execute(httpMethod);
            // 获取返回结果
            if (null != httpResponse && null != httpResponse.getStatusLine()) {
                String content = "";
                HttpEntity entity = httpResponse.getEntity();
                if (null != entity) {
                    if (null == charset) {
                        content = EntityUtils.toString(entity);
                    } else {
                        content = EntityUtils.toString(entity, charset);
                    }
                }
                return new HttpClientResult(httpResponse.getStatusLine().getStatusCode(), content);
            }
            return new HttpClientResult(HttpStatus.SC_INTERNAL_SERVER_ERROR);
        } finally {
            // 释放资源
            this.release(httpResponse);
        }
    }

    /**
     * @description 响应释放
     * <p> <功能详细描述> </p>
     *
     * @author 陈晨
     * @date 2023/4/12 16:47
     */
    private void release(CloseableHttpResponse response) {
        if (null == response) {
            return;
        }
        try {
            response.close();
        } catch (IOException e) {
            log.error("HttpResponse释放资源失败", e);
        }
    }

    /**
     * @description 获取默认请求头
     * <p> <功能详细描述> </p>
     *
     * @author 陈晨
     * @date 2023/4/12 16:47
     */
    private List<Header> getDefaultHeaders() {
        List<Header> headers = new ArrayList<>();
        headers.add(new BasicHeader("User-Agent",
                "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36"));
        headers.add(new BasicHeader("Accept-Encoding", "gzip,deflate"));
        headers.add(new BasicHeader("Accept-Language", "zh-CN"));
        headers.add(new BasicHeader("Connection", "Keep-Alive"));
        return headers;
    }

}


